/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-10
 * V4.0
 */
package com.jphenix.clazz;

import com.jphenix.clazz.annotation.Annotation;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.ClassInfo;
import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


/**
 * <code>ClassFile</code> represents a Java <code>.class</code> file, which
 * consists of a constant pool, methods, fields, and attributes.
 * 
 * 2019-04-30 增加了JDK1.8 major标识变量
 * 
 * @author mabg
 * @see //javassist.CtClass#getClassFile()
 */
@ClassInfo({"2019-04-30 10:36","解析主类"})
public final class ClassFile {
    
    int major, minor; // version number
    ConstPool constPool;
    int thisClass;
    int accessFlags;
    int superClass;
    int[] interfaces;
    ArrayList<FieldInfo> fields;
    ArrayList<MethodInfo> methods;
    ArrayList<AttributeInfo> attributes;
    boolean needOutput = false; //是否需要输出类
    String thisClassName; // not JVM-internal name
    String[] cachedInterfaces;
    String cachedSuperclass;
    byte[] classBytes; //类字节数组
    int bufferSize = 10240; //读取缓存大小
    
    /**
     * The major version number of class files
     * for JDK 1.1.
     */
    public static final int JAVA_1 = 45;

    /**
     * The major version number of class files
     * for JDK 1.2.
     */
    public static final int JAVA_2 = 46;

    /**
     * The major version number of class files
     * for JDK 1.3.
     */
    public static final int JAVA_3 = 47;

    /**
     * The major version number of class files
     * for JDK 1.4.
     */
    public static final int JAVA_4 = 48;

    /**
     * The major version number of class files
     * for JDK 1.5.
     */
    public static final int JAVA_5 = 49;

    /**
     * The major version number of class files
     * for JDK 1.6.
     */
    public static final int JAVA_6 = 50;

    /**
     * The major version number of class files
     * for JDK 1.7.
     */
    public static final int JAVA_7 = 51;
    
    /**
     * The major version number of class files
     * for JDK 1.8.
     */
    public static final int JAVA_8 = 52;

    /**
     * The major version number of class files created
     * from scratch.  The default value is 47 (JDK 1.3).
     * It is 49 (JDK 1.5)
     * if the JVM supports <code>java.lang.StringBuilder</code>.
     * It is 50 (JDK 1.6)
     * if the JVM supports <code>java.util.zip.DeflaterInputStream</code>.
     * It is 51 (JDK 1.7)
     * if the JVM supports <code>java.lang.invoke.CallSite</code>.
     */
    public static int MAJOR_VERSION = JAVA_3;

    static {
        try {
            Class.forName("java.lang.StringBuilder");
            MAJOR_VERSION = JAVA_5;
            Class.forName("java.util.zip.DeflaterInputStream");
            MAJOR_VERSION = JAVA_6;
            Class.forName("java.lang.invoke.CallSite");
            MAJOR_VERSION = JAVA_7;
        }
        catch (Throwable t) {}
    }

    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public ClassFile(DataInputStream in) throws IOException {
        super();
        read(in);
    }
    
    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public ClassFile(String filePath) throws Exception {
        super();
        read(new DataInputStream(SFilesUtil.getFileInputStreamByUrl(filePath,null)));
    }
    
    
    /**
     * 构造函数
     * @author MBG
     */
    public ClassFile(byte[] data) throws Exception {
    	super();
    	if(data==null) {
    		return;
    	}
    	read(new DataInputStream(new ByteInputStream(data,data.length)));
    }
    
    /**
     * 构造函数
     * @param filePath 类文件路径
     * @param needOutput 是否需要输出，比如获取该类的类实例，获取类的字节数组等等
     *      如果需要输出，程序会将类的内容缓存到实例中，默认不输出，即不缓存
     * @author 马宝刚
     */
    public ClassFile(String filePath,boolean needOutput) throws Exception {
        super();
        this.needOutput = needOutput;
        read(new DataInputStream(SFilesUtil.getFileInputStreamByUrl(filePath,null)));
    }
    
    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public ClassFile(Class<?> cls) throws IOException {
        super();
        if(cls==null) {
            return;
        }
        //文件相对路径
        String fileName = cls.getName().replace(".","/")+".class"; 
        
        //获取读入流
        InputStream is = cls.getClassLoader().getResourceAsStream(fileName);
        if(is==null) {
            return;
        }
        read(new DataInputStream(is));
    }

    /**
     * Returns a constant pool table.
     */
    public ConstPool getConstPool() {
        return constPool;
    }

    /**
     * Returns true if this is an interface.
     */
    public boolean isInterface() {
        return (accessFlags & AccessFlag.INTERFACE) != 0;
    }

    /**
     * Returns true if this is a final class or interface.
     */
    public boolean isFinal() {
        return (accessFlags & AccessFlag.FINAL) != 0;
    }

    /**
     * Returns true if this is an abstract class or an interface.
     */
    public boolean isAbstract() {
        return (accessFlags & AccessFlag.ABSTRACT) != 0;
    }

    /**
     * Returns access flags.
     * 
     * @see //javassist.bytecode.AccessFlag
     */
    public int getAccessFlags() {
        return accessFlags;
    }


    /**
     * Returns access and property flags of this nested class.
     * This method returns -1 if the class is not a nested class. 
     *
     * <p>The returned value is obtained from <code>inner_class_access_flags</code>
     * of the entry representing this nested class itself
     * in <code>InnerClasses_attribute</code>>. 
     */
    public int getInnerAccessFlags() {
        InnerClassesAttribute ica
            = (InnerClassesAttribute)getAttribute(InnerClassesAttribute.tag);
        if (ica == null) {
            return -1;
        }
        String name = getName();
        int n = ica.tableLength();
        for (int i = 0; i < n; ++i) {
            if (name.equals(ica.innerClass(i))) {
                return ica.accessFlags(i);
            }
        }
        return -1;
    }

    /**
     * Returns the class name.
     */
    public String getName() {
        return thisClassName;
    }

    /**
     * Returns the super class name.
     */
    public String getSuperclass() {
        if (cachedSuperclass == null) {
            cachedSuperclass = constPool.getClassInfo(superClass);
        }

        return cachedSuperclass;
    }

    /**
     * Returns the index of the constant pool entry representing the super
     * class.
     */
    public int getSuperclassId() {
        return superClass;
    }


    /**
     * Returns the names of the interfaces implemented by the class.
     * The returned array is read only.
     */
    public String[] getInterfaces() {
        if (cachedInterfaces != null) {
            return cachedInterfaces;
        }

        String[] rtn = null;
        if (interfaces == null) {
            rtn = new String[0];
        } else {
            int n = interfaces.length;
            String[] list = new String[n];
            for (int i = 0; i < n; ++i) {
                list[i] = constPool.getClassInfo(interfaces[i]);
            }
            rtn = list;
        }

        cachedInterfaces = rtn;
        return rtn;
    }
    


    /**
     * Returns all the fields declared in the class.
     * 
     * @return a list of <code>FieldInfo</code>.
     * @see FieldInfo
     */
    public List<FieldInfo> getFields() {
        return fields;
    }

    /**
     * Returns all the methods declared in the class.
     * 
     * @return a list of <code>MethodInfo</code>.
     * @see MethodInfo
     */
    public List<MethodInfo> getMethods() {
        return methods;
    }

    /**
     * Returns the method with the specified name. If there are multiple methods
     * with that name, this method returns one of them.
     * 
     * @return null if no such method is found.
     */
    public MethodInfo getMethod(String name) {
        if(methods==null) {
            return null;
        }
        ArrayList<MethodInfo> list = methods;
        for (MethodInfo minfo:list) {
            if (minfo.getName().equals(name)) {
                return minfo;
            }
        }
        return null;
    }

    /**
     * Returns a static initializer (class initializer), or null if it does not
     * exist.
     */
    public MethodInfo getStaticInitializer() {
        return getMethod(MethodInfo.nameClinit);
    }


    /**
     * Returns all the attributes.  The returned <code>List</code> object
     * is shared with this object.  If you add a new attribute to the list,
     * the attribute is also added to the classs file represented by this
     * object.  If you remove an attribute from the list, it is also removed
     * from the class file.
     * 
     * @return a list of <code>AttributeInfo</code> objects.
     * @see AttributeInfo
     */
    public List<AttributeInfo> getAttributes() {
        return attributes;
    }

    /**
     * Returns the attribute with the specified name.  If there are multiple
     * attributes with that name, this method returns either of them.   It
     * returns null if the specified attributed is not found.
     * 
     * @param name          attribute name
     * @see #getAttributes()
     */
    public AttributeInfo getAttribute(String name) {
        ArrayList<AttributeInfo> list = attributes;
        for (AttributeInfo ai:list) {
            if (ai.getName().equals(name)) {
                return ai;
            }
        }
        return null;
    }


    /**
     * Returns the source file containing this class.
     * 
     * @return null if this information is not available.
     */
    public String getSourceFile() {
        SourceFileAttribute sf = (SourceFileAttribute)getAttribute(SourceFileAttribute.tag);
        if (sf == null) {
            return null;
        } else {
            return sf.getFileName();
        }
    }
    

    /**
     * 读取并解析当前类
     * @param in 数据读入流
     * @throws IOException 异常
     * 2014年6月21日
     * @author 马宝刚
     */
    private void read(DataInputStream in) throws IOException {
        if(in==null) {
            return;
        }
        /*
         * 如果需要输出类，比如获取该类的对象，该类的字节数组，就需要在解析类之前，将
         * 该类读取好放入字节数组中。目前该功能不支持修改类的内容
         */
        if(needOutput) {
            //构造类字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream(bufferSize);
            try {
                byte[] buffer = new byte[bufferSize];
                int bytesRead = -1; //读取字节
                while ((bytesRead = in.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
                out.flush();
            }finally {
                try {
                    in.close();
                }catch (IOException ex) {
                    ex.printStackTrace();
                }
                try {
                    out.close();
                }catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            classBytes = out.toByteArray();
            in = new DataInputStream(new ByteInputStream(classBytes,classBytes.length));
        }
        int i, n;
        int magic = in.readInt();
        if (magic != 0xCAFEBABE) {
            throw new IOException("bad magic number: " + Integer.toHexString(magic));
        }

        minor = in.readUnsignedShort();
        major = in.readUnsignedShort();
        constPool = new ConstPool(in);
        accessFlags = in.readUnsignedShort();
        thisClass = in.readUnsignedShort();
        constPool.setThisClassInfo(thisClass);
        superClass = in.readUnsignedShort();
        n = in.readUnsignedShort();
        if (n == 0) {
            interfaces = null;
        } else {
            interfaces = new int[n];
            for (i = 0; i < n; ++i) {
                interfaces[i] = in.readUnsignedShort();
            }
        }
        ConstPool cp = constPool;
        n = in.readUnsignedShort();
        fields = new ArrayList<FieldInfo>();
        for (i = 0; i < n; ++i) {
            fields.add(new FieldInfo(cp, in));
        }

        n = in.readUnsignedShort();
        methods = new ArrayList<MethodInfo>();
        for (i = 0; i < n; ++i) {
            methods.add(new MethodInfo(cp, in));
        }

        attributes = new ArrayList<AttributeInfo>();
        n = in.readUnsignedShort();
        for (i = 0; i < n; ++i) {
            attributes.add(AttributeInfo.read(cp, in));
        }
        thisClassName = constPool.getClassInfo(thisClass);
    }


    /**
     * Get the Major version.
     * 
     * @return the major version
     */
    public int getMajorVersion() {
        return major;
    }


    /**
     * Get the minor version.
     * 
     * @return the minor version
     */
    public int getMinorVersion() {
        return minor;
    }
    
    
    /**
     * 获取包名
     * @return
     * 2014年6月10日
     * @author 马宝刚
     */
    public String getPackage() {
        String name = getName();
        if(name==null) {
            return "";
        }
        int point = name.lastIndexOf(".");
        if(point>0) {
            return name.substring(0,point);
        }
        return "";
    }
        
    /**
     * 获取指定类，指定方法传入参数名
     * @param cls  指定类
     * @param methodName 方法名
     * @return 传入参数名
     * @throws Exception 异常
     * 2014年6月11日
     * @author 马宝刚
     */
    public static String[] getMethodParameterNames(
                            Class<?> cls,String methodName) throws Exception {
        //构建类解析器
        ClassFile cf = new ClassFile(cls);
        //获取指定方法对象信息
        MethodInfo md = cf.getMethod(methodName);
        if(md==null) {
            return new String[0];
        }
        return md.getParameterNames();
    }
    
    
    /**
     * 获取类对象
     * 前提是在构造当前类实例时，需要传入needOutput为true，否则返回null
     * @param bcl                   获取统一类加载器
     * @return                         类对象
     * @throws Exception        异常
     * 2014年6月21日
     * @author 马宝刚
     */
    public Class<?> toClass(IByteClassLoader bcl) throws Exception {
        return bcl.loadClass(thisClassName,classBytes,false);
    }
    
    /**
     * 获取类的字节数组
     * 前提是在构造当前类实例时，需要传入needOutput为true，否则返回null
     * @return 类的字节数组
     * 2014年6月21日
     * @author 马宝刚
     */
    public byte[] getClassBytes() {
        return classBytes;
    }
    
    
    /**
     * 判断当前类，是否包含指定接口（无法判断当前类继承的父类中实现了这个接口）
     * @param cls 指定接口
     * @return 是否包含
     * 2016年4月15日
     * @author MBG
     */
    public boolean hasInterface(Class<?> cls) {
    	if(cls==null) {
    		return false;
    	}
    	String clsName = cls.getName(); //获取类名
    	String[] clsArr = getInterfaces(); //获取当前类引用的接口类名
    	for(String ele:clsArr) {
    		if(clsName.equals(ele)) {
    			return true;
    		}
    	}
    	return false;
    }
    
    
    public Object getAnnotationType(Class<?> clz) throws Exception {
    	if(clz==null) {
    		return null;
    	}
    	String typeName = clz.getName();
    	AnnotationsAttribute ainfo = 
        		   (AnnotationsAttribute)getAttribute(AnnotationsAttribute.invisibleTag);
		if(ainfo!=null) {
			Annotation[] anno1 = ainfo.getAnnotations();
    		for (int i=0;i<anno1.length;i++) {
    			if (anno1[i].getTypeName().equals(typeName)) {
    				return anno1[i].toAnnotationType();
    			}
    		}
    	}
    	AnnotationsAttribute ainfo2 = 
        		   (AnnotationsAttribute)getAttribute(AnnotationsAttribute.visibleTag);
    	if(ainfo2!=null) {
    		Annotation[] anno2 = ainfo2.getAnnotations();
    		for (int i = 0; i < anno2.length; i++) {
    			if (anno2[i].getTypeName().equals(typeName)) {
    				return anno2[i].toAnnotationType();
    			}
    		}
    	}
    	return null;
	}
}
